package com.rayframework.context.annontion;

import com.rayframework.beans.factory.annotation.RayAutowired;
import com.rayframework.beans.factory.annotation.RayQualifier;
import com.rayframework.beans.factory.config.AnnotationBeanDefinition;

import com.rayframework.context.stereotype.RayComponent;
import com.rayframework.context.support.GenericApplicationContext;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 基于注解的配置方式<br/>
 * 非延时加载器 - 注解实现
 * 每个配置类上使用 {@link RayConfiguration @RayConfiguration}注解标注，并在其中定义需要创建的Bean和它们的属性值。
 * 通过Java代码的方式，可以更加灵活地管理Bean的定义和属性值，并且不需要编写XML配置文件
 *
 * @author LiaoYuXing-Ray
 * @version 1.0
 * @createDate 2023/11/25 15:50
 **/
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {

    /**
     * 2023/9/20 13:18
     * Ray 容器的缓存池 - 存储和管理 Bean 对象
     * 可理解为 Spring IOC容器的缓存池（低配版）
     * <br/>
     * Bean实例化缓存Map
     * key：接口对象 或 类对象
     * value：Bean管理工厂，里面存放需要注入的Bean的基本信息
     */
    private static final Map<Class<?>, AnnotationBeanDefinition> beanMap = new ConcurrentHashMap<>();

    public static Map<Class<?>, AnnotationBeanDefinition> getBeanMap() {
        return beanMap;
    }


    @Override
    public Object getBean(String beanName) throws Exception {
        return null;
    }

    @Override
    public boolean containsBean(Class<?> clazz) {
        return false;
    }

    @Override
    public boolean containsBean(String beanName) {
        return false;
    }


    private static String rootPath;


    /**
     * 构造方法
     * 传递要扫描的包路径， 扫描参数中指定包及其子包里面所有类中的  {@link RayComponent @RayComponent}注解
     * 并将注解所标注的类通过反射进行实例化，然后对 {@link RayAutowired @RayAutowired}所标注的属性进行自动装配
     *
     * @param annotationPackagePath 要扫描的包路径
     * @author LiaoYuXing-Ray 2023/11/25 17:11
     **/
    public AnnotationConfigApplicationContext(String annotationPackagePath) {
        // 获取多个路径
        List<String> packagePaths = new ArrayList<>(Arrays.asList(annotationPackagePath.split(";")));
        // 加载RayMyBatisPlus融合注解
//        if (RayBootUtils.checkPropertyUse("Ray.init.MyBatisPlus.use")) {
//            try {
//                RayAnnotationHelper.loadRayMyBatisPlusAnnotation();
//            }catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException ex) {
//                RayConsoleUtils.err("找不到类；[com.baomidou.mybatisplus.extension.service.impl.ServiceImpl]",ex.getMessage());
//            }
//        }

        try {
            for (String tempPath : packagePaths) {
                // 1、 把路径中的.替换成\
                String packagePath = tempPath.replaceAll("\\.", "\\\\");


                // 2、 获取包的绝对路径
                Enumeration<URL> packagePathUrl = Thread.currentThread().getContextClassLoader().getResources(packagePath);

                while (packagePathUrl.hasMoreElements()) {
                    URL url = packagePathUrl.nextElement();
                    String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8.name());

                    // 获取包前面的路径部分，字符串截取
                    rootPath = filePath.substring(0, filePath.length() - packagePath.length());

                    // 包扫描
                    loadRayAnnotation(new File(filePath));
                    // 依赖注入
                    autowired();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 扫描参数中指定包及其子包里面所有类中的 {@link RayComponent} 注解，并将注解所标注的类通过反射进行实例化
     * 递归
     */
    private static void loadRayAnnotation(File file) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 1、 判断file对象是否是文件夹
        if (file.isDirectory()) {
            // 2、 获取文件夹里面的所有内容
            File[] childFiles = file.listFiles();
            // 3、 若文件夹里面为空，直接返回，若不为空，则遍历文件夹
            if (childFiles == null || childFiles.length == 0) {
                return;
            }
            // 3.1、 遍历得到每个File对象
            for (File childFile : childFiles) {
                // 3.2、 继续判断，如果还是文件，递归
                if (childFile.isDirectory()) {
                    loadRayAnnotation(childFile);
                } else {
                    // 3.3、 当遍历得到的File对象不是文件夹，是一个文件，获取“包路径+类名"部分，
                    String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);
                    // 3.4、 判断当前文件类型是否是.class文件
                    if (pathWithClass.contains(".class")) {
                        // 3.5、 如果是.class类型，把路径'\'替换成'.'，并把.class去掉
                        String allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
                        // 3.6、 获取类的Class对象，判断该类是否是接口
                        Class<?> clazz = Class.forName(allName);
                        // 筛选不是接口的Class
                        if (!clazz.isInterface()) {
                            // 3.7、 判断该非接口的是否标注了 @RayComponent 注解，如果有，则实例化该类的对象 控制反转
                            RayComponent rayComponent = clazz.getAnnotation(RayComponent.class);
                            // 判断该非接口的类上是否标注了 @RayQualifier 注解，如果有，需要设置到Bean管理工厂中
                            RayQualifier rayQualifier = clazz.getAnnotation(RayQualifier.class);
                            if (rayComponent != null) {
                                // 这样操作可以让beanMap中的实例对象是单例的，因为注入只用这个对象
                                Object instance = clazz.getConstructor().newInstance();
                                /*
                                    3.8、 判断该类是否实现了接口，如果实现了接口，就把对应接口的class作为key，
                                          如果没有实现接口，就将该类自己的class作为key，保存到 beanMap 中。

                                    若接口有多个实现类，使用指定了 @RayQualifier 注解的实现类。
                                    如果不指定具体的实现类，实例对象会被后面的实现类代替，可能造成与预期不符合的情况
                                 */

                                // 该类实现了接口
                                if (clazz.getInterfaces().length > 0) {
                                    /*
                                     * 如果已经有了这个 clazz 实现的接口的Bean (只有接口会有多个实现类的可能) 2023/9/20 17:53
                                     * 如果实现的接口不写在第一个，那不行
                                     */
                                    if (beanMap.containsKey(clazz.getInterfaces()[0])) {
                                        AnnotationBeanDefinition beanFactory = beanMap.get(clazz.getInterfaces()[0]);
                                        // 如果有两个及以上实现同一个接口，但是没有用 @RayQualifier 注解说明，发送警告
                                        // 其实如果不使用注解说明，本框架还是能按照加载的最后一个去实例化的 TODO 纠结一下，到底要不要强行终止程序运行
                                        if (rayQualifier == null) {
                                            System.out.println("***************************\n" +
                                                    "   RAY APPLICATION WARN\n" +
                                                    "***************************\n" +
                                                    "Warn Description:\n" +
                                                    "\n" +
                                                    "接口:\t" + clazz.getInterfaces()[0].getName() + " in " + Objects.requireNonNull(clazz.getResource("")).getPath() + " 需要一个单一的bean, 但是找到了>=2个" +
                                                    "\n\n" +
                                                    "Action:" +
                                                    "\n\n" +
                                                    "请考虑使用 @RayQualifier 注解来识别应该被消费的bean，否则框架默认使用最后一个注入的bean[" + beanFactory.getComponentObjectMap().get(beanFactory.getAliasFor()).getClass().getName() + "]进行实例化");
                                            continue;
                                        }

                                        // 同一个接口的实现类的 @RayQualifier 使用两个以上的重复value值
                                        if (rayQualifier.value().equals(beanFactory.getAliasFor())) {
                                            System.out.println("***************************\n" +
                                                    "   RAY APPLICATION WARN\n" +
                                                    "***************************\n" +
                                                    "Warn Description:\n" +
                                                    "\n" +
                                                    "接口:\t" + clazz.getInterfaces()[0].getName() + " 的实现类的 @RayQualifier 注解value值[" + rayQualifier.value() + "]重复" +
                                                    "\n\n" +
                                                    "Action:" +
                                                    "\n\n" +
                                                    "请修改value的值，否则框架默认使用最后一个注入的bean[" + beanFactory.getComponentObjectMap().get(beanFactory.getAliasFor()).getClass().getName() + "]进行实例化");
                                            continue;
                                        }

                                        // 如果value值为默认的空字符串，就会被替换实例化依赖对象
                                        beanFactory.getComponentObjectMap().put(rayQualifier.value(), instance);
                                        beanMap.put(clazz.getInterfaces()[0], beanFactory); // 增加Bean存放到容器缓存池中

                                        continue;
                                    }


                                    /*
                                     * 如果这个 clazz 实现的接口为 [第1次] 存放Bean (只有接口会有多个实现类的可能) 2023/9/20 17:53
                                     */
                                    AnnotationBeanDefinition beanFactory = new AnnotationBeanDefinition();
                                    beanFactory.setInterfaceImpl(true); // 标记为实现接口
                                    // @RayQualifier 注解不为空，则使用了别名，且可以为默认的空字符串
                                    if (rayQualifier != null) {
                                        beanFactory.setUseQualifier(true); // 标记为使用别名
                                        beanFactory.setAliasFor(rayQualifier.value()); // 设置注入依赖项的别名
                                        Map<String, Object> tempBeanMap = new ConcurrentHashMap<>();
                                        tempBeanMap.put(rayQualifier.value(), instance);
                                        beanFactory.setComponentObjectMap(tempBeanMap); // 设置依赖对象集合
                                    } else {
                                        // [第1次] 可以不使用依赖项的别名
                                        Map<String, Object> tempBeanMap = new ConcurrentHashMap<>();
                                        tempBeanMap.put("", instance); // 设置依赖项的别名的默认别名为空字符串
                                        beanFactory.setComponentObjectMap(tempBeanMap); // 设置依赖项
                                    }
                                    beanMap.put(clazz.getInterfaces()[0], beanFactory); // Bean存放容器缓存池中


                                } else {
                                    // 该类没有实现接口
                                    AnnotationBeanDefinition beanFactory = new AnnotationBeanDefinition();
                                    // 只有接口对象的实现类在注入的时候需要辨别 @RayQualifier 的值，所以直接存放Bean
                                    beanFactory.setInterfaceImpl(false); // 保险起见
                                    beanFactory.setUseQualifier(false); // 保险起见
                                    beanFactory.setComponentObject(instance); // 未实现接口使用的实例化Bean对象
                                    beanMap.put(clazz, beanFactory); // Bean存放容器缓存池中
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * DI 依赖注入
     * 对 {@link RayAutowired} 所标注的属性进行自动装配
     */
    private void autowired() throws IllegalAccessException {
        // bean实例化对象均保存在 beanMap 集合中
        // 1、遍历 beanMap 的map集合
        Set<Map.Entry<Class<?>, AnnotationBeanDefinition>> entrySet = beanMap.entrySet();
        for (Map.Entry<Class<?>, AnnotationBeanDefinition> beanDefinition : entrySet) {
            // 2、得到map集合每个元素的value，并获取到value对象内的实例化Bean属性
            Object obj;
            AnnotationBeanDefinition beanTempFactory = beanDefinition.getValue();
            // 如果实例化对象实现了接口
            if (beanTempFactory.isInterfaceImpl()) {
                // 如果使用了依赖项别名
                if (beanTempFactory.isUseQualifier()) {
                    obj = beanTempFactory.getComponentObjectMap().get(beanTempFactory.getAliasFor());
                } else {
                    // 默认依赖项别名为空字符串
                    obj = beanTempFactory.getComponentObjectMap().get("");
                }
            } else {
                // 如果实例化对象没有实现接口，就使用 getComponentObject
                obj = beanTempFactory.getComponentObject();
            }
            Class<?> clazz = obj.getClass();
            Field[] fields = clazz.getDeclaredFields();
            // 3、遍历得到的每个对象的属性数组，得到每个属性
            for (Field field : fields) {
                // 4、判断属性上是否标注了 @RayAutowired 注解，如果有，把对象进行设置（属性注入）
                RayAutowired autowired = field.getAnnotation(RayAutowired.class);
                // 判断属性上是否标注了 @RayQualifier 注解，如果有，把对应的value的对象进行设置（属性注入）
                RayQualifier qualifier = field.getAnnotation(RayQualifier.class);
                if (autowired != null && autowired.required()) {
                    // 设置私有属性允许访问
                    field.setAccessible(true);
                    AnnotationBeanDefinition beanFactory = beanMap.get(field.getType());

                    // 如果实例化对象实现了接口
                    if (beanFactory.isInterfaceImpl()) {
                        // 如果使用 @RayQualifier 注解，设置对应的value的对象进行注入
                        if (qualifier != null) {
                            if (qualifier.value().equals(beanFactory.getAliasFor())) {
                                field.set(obj, beanFactory.getComponentObjectMap().get(qualifier.value())); // 将beanMap中与字段类型相对应的值设置为字段的值 单例
                            } else {
                                // 属性带有 @RayAutowired 注解下的 @RayQualifier 的value值无法识别
                                System.out.println("***************************\n" +
                                        "   RAY APPLICATION WARN\n" +
                                        "***************************\n" +
                                        "Warn Description:\n" +
                                        "\n" +
                                        "属性:\t" + field.getName() + " 带有 @RayAutowired 注解，但其下的 @RayQualifier 注解的value值[" + qualifier.value() + "]无法识别" +
                                        "\n\n" +
                                        "Action:" +
                                        "\n\n" +
                                        "请检查并修改value的值，例如使用[" + beanFactory.getAliasFor() + "]，否则框架默认使用最后一个注入的bean[" + beanFactory.getComponentObjectMap().get(beanFactory.getAliasFor()).getClass().getName() + "]进行实例化");
                                field.set(obj, beanFactory.getComponentObjectMap().get(beanFactory.getAliasFor())); // 将beanMap中与字段类型相对应的值设置为字段的值 单例
                            }
                            continue;
                        }
                        // 如果没有设置别名，使用最后一个加载的实现类的实例对象
                        field.set(obj, beanFactory.getComponentObjectMap().get(beanFactory.getAliasFor())); // 将beanMap中与字段类型相对应的值设置为字段的值 单例
                        continue;
                    }
                    // 如果实例化对象未实现任何接口
                    field.set(obj, beanFactory.getComponentObject()); // 将beanMap中与字段类型相对应的值设置为字段的值 单例
                }
            }
        }
    }

    @Override
    public void register(Class<?>... componentClasses) {

    }

    @Override
    public void scan(String... basePackages) {

    }
}
